import { b2BodyType, b2CircleShape, b2Contact, b2ContactFilter, b2ContactImpulse, b2ContactListener, b2EdgeShape, b2Filter, b2Manifold, b2PolygonShape, b2Vec2, b2World } from "@box2d/core";
import { invertMatrix, Matrix } from "../../math";
import { RigidBody } from "../../RigidBody";
import { Behaviour } from "../Behaviour";
import { Camera } from "../Camera";
import { BoxCollider, CircleCollider, EdgeCollider } from "../Collider";
import { GameObject } from "../GameObject";
import { System } from "../System";
import { Transform } from "../Transform";
import { PhysicalDebugDraw } from "./PhysicalDebugDraw";
import { Settings } from "./settings";
import { g_camera } from "./utils/camera";
import { g_debugDraw } from "./utils/draw";

export class PhysicalSystem extends System {

    public readonly m_settings = new Settings();

    public debugDraw: PhysicalDebugDraw | null = null;

    public world: b2World;

    camera: Camera;

    private isPause = false;
    private listener: b2ContactListener;

    constructor(isPause: boolean, listener?: b2ContactListener) {
        super();
        this.isPause = isPause;
        this.listener = listener;
        const gravity = { x: 0, y: -10 }
        this.world = b2World.Create(gravity);
    }

    onAddBehaviour(gameObject: GameObject, behaviour: Behaviour) {
        if (behaviour instanceof Camera) {
            this.camera = behaviour;
        }
        if (behaviour instanceof RigidBody) {

            behaviour.b2body = this.world.CreateBody({
                position: { x: behaviour.x, y: behaviour.y },
                type: behaviour.type,
                allowSleep: false,
            });
            behaviour.b2body.SetUserData(behaviour.gameObject);

        }
        if (behaviour instanceof CircleCollider) {
            const rigid = behaviour.gameObject.getBehaviour(RigidBody);
            const shape = new b2CircleShape();
            shape.m_radius = behaviour.radius;
            rigid.b2body.CreateFixture({
                shape,
                density: 20,
                friction: 0,
                isSensor: rigid.sensor,
            })
            behaviour.shape = rigid.b2body.GetFixtureList().GetShape() as b2CircleShape;

        }
        if (behaviour instanceof EdgeCollider) {
            const rigid = behaviour.gameObject.getBehaviour(RigidBody);
            const shape = new b2EdgeShape();
            shape.SetTwoSided(new b2Vec2(behaviour.startX, behaviour.startY), new b2Vec2(behaviour.endX, behaviour.endY));
            rigid.b2body.CreateFixture({
                shape,
                friction: 0,
                isSensor: rigid.sensor
            })
            behaviour.shape = rigid.b2body.GetFixtureList().GetShape() as b2EdgeShape;

        }
        if (behaviour instanceof BoxCollider) {
            const rigid = behaviour.gameObject.getBehaviour(RigidBody);
            const b2body = rigid.b2body;
            const shape = new b2PolygonShape();
            shape.SetAsBox(behaviour.edge / 2, behaviour.edge / 2, new b2Vec2(0, 0), 0);
            b2body.CreateFixture({
                shape,
                density: 20,
                friction: 0,
                isSensor: rigid.sensor
            });
            behaviour.shape = rigid.b2body.GetFixtureList().GetShape() as b2PolygonShape;
        }
    }

    onRemoveBehaviour(gameObject: GameObject, behaviour: Behaviour) {
        //console.log('remove', behaviour)

        if (behaviour instanceof Camera) {
            this.camera = null;
        }
        if (behaviour instanceof RigidBody) {
            this.world.DestroyBody(behaviour.b2body);
        }
        if (behaviour instanceof CircleCollider) {

        }
        if (behaviour instanceof EdgeCollider) {

        }
        if (behaviour instanceof BoxCollider) {

        }

    }


    onStart() {
        g_camera.resize(600, 600);
        this.debugDraw = new PhysicalDebugDraw();
        const multiListener = new MultiContactListener();
        multiListener.add(this.debugDraw);
        if (this.listener) {
            multiListener.add(this.listener)
        }
        this.world.SetContactListener(multiListener);
        const zoom = 50;
        const center = b2Vec2.ZERO;
        g_camera.setPositionAndZoom(center.x, center.y, zoom);

    }

    onTick(duringTime: number): void {
        const world = this.world;
        const settings = this.m_settings;
        world.SetAllowSleeping(settings.m_enableSleep);

        world.SetWarmStarting(settings.m_enableWarmStarting);
        world.SetContinuousPhysics(settings.m_enableContinuous);
        world.SetSubStepping(settings.m_enableSubStepping);
        const timeStep = duringTime / 1000;

        if (!this.isPause) {
            world.Step(timeStep, {
                velocityIterations: settings.m_velocityIterations,
                positionIterations: settings.m_positionIterations,
            });
        }

        for (let b2body = world.GetBodyList(); b2body; b2body = b2body.GetNext()) {
            const b2Transform = b2body.GetTransform();
            const gameObject = b2body.GetUserData() as GameObject;
            const transform = gameObject.getBehaviour(Transform)
            transform.x = b2Transform.p.x * 50;
            transform.y = -b2Transform.p.y * 50;
        }
    }

    onFrame() {
        const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
        g_debugDraw.m_ctx = canvas.getContext("2d");
        const world = this.world;
        const settings = this.m_settings;

        let viewportMatrix = new Matrix();
        if (this.camera) {
            const cameraTransform = this.camera.gameObject.getBehaviour(Transform)
            viewportMatrix = invertMatrix(cameraTransform.globalMatrix);
        }
        this.debugDraw.draw(world, settings, viewportMatrix);
    }
}

export class MultiContactListener extends b2ContactListener {

    private list: b2ContactListener[] = [];

    add(listener: b2ContactListener) {
        this.list.push(listener);
    }

    BeginContact(_contact: b2Contact) {
        this.list.map(l => l.BeginContact(_contact));
    };

    EndContact(_contact: b2Contact) {
        this.list.map(l => l.EndContact(_contact));
    };

    PreSolve(_contact: b2Contact, _oldManifold: b2Manifold) {
        this.list.map(l => l.PreSolve(_contact, _oldManifold));
    };

    PostSolve(_contact: b2Contact, _impulse: b2ContactImpulse) {
        this.list.map(l => l.PostSolve(_contact, _impulse));
    };
}